﻿using System;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;

namespace PhiStore
{
    /// <summary>
    /// Static methods for receiving broadcast from PhiServers
    /// </summary>
    public static class ReceiveBroadcast
    {

        /// <summary>
        /// Receives a broadcast or throws an exception if not successful
        /// </summary>
        /// <param name="port">the port to receive at</param>
        /// <param name="attempts">number of attemps before exception</param>
        /// <returns></returns>
        public static BroadcastObject Receive(int port, int attempts)
        {
            BroadcastObject result = null;
            bool gotBroadCast = false;
            int noOfAttempts = 0;
            while (!gotBroadCast && noOfAttempts++ < attempts)
            {
                UdpClient udp = null;
                try
                {
                    MemoryStream stream = new MemoryStream();


                    IPAddress multicastaddress = IPAddress.Parse("239.123.123.123");

                    IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, port);

                    udp = new UdpClient();
                    udp.Client.Bind(endpoint);
                    udp.Client.ReceiveTimeout = 5000;

                    udp.JoinMulticastGroup(multicastaddress);

                    byte[] bytes = udp.Receive(ref endpoint);

                    stream.Write(bytes, 0, bytes.Length);
                    stream.Position = 0;
                    BinaryFormatter formatter = new BinaryFormatter();

                    result = (BroadcastObject)formatter.Deserialize(stream);
                    gotBroadCast = true;

                    stream.Close();
                }
                catch (Exception e)
                {
                    if (e is SocketException && (e as SocketException).ErrorCode == 10048) //socket already in use
                        Thread.Sleep(1000); //wait a bit and try again

                    Log.Instance.AddLogEntry("ReceiveBroadcast", "No broadcast received. Attempt no " + noOfAttempts);
                }
                finally
                {
                    if (udp != null) udp.Close();
                }
                
            }
            if (result == null) //it failed
                throw new WebException("Did not receive a broadcast within " + noOfAttempts + " attempts");

            return result;
        }

        /// <summary>
        /// http://stackoverflow.com/questions/570098/in-c-how-to-check-if-a-tcp-port-is-available
        /// </summary>
        /// <param name="port"></param>
        /// <returns></returns>
        public static bool isPortAvailable(int port)
        {
            bool isAvailable = true;

            IPGlobalProperties ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
            TcpConnectionInformation[] tcpConnInfoArray = ipGlobalProperties.GetActiveTcpConnections();
            foreach (TcpConnectionInformation tcpi in tcpConnInfoArray)
            {
                if (tcpi.LocalEndPoint.Port == port)
                {
                    isAvailable = false;
                    Log.Instance.AddLogEntry("ReceiveBroadcast", "port: " + port + " is not available");
                    break;
                }
            }
            return isAvailable;
        }

       
    }
}
